package com.masonluo.mlonlinejudge.acl.proxy;

import com.masonluo.mlonlinejudge.acl.annotations.RequireAuthentication;
import com.masonluo.mlonlinejudge.acl.annotations.RequireRoles;
import com.masonluo.mlonlinejudge.acl.core.*;
import com.masonluo.mlonlinejudge.acl.core.filter.FilterResult;
import com.masonluo.mlonlinejudge.acl.utils.ObjectUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

/**
 * @author masonluo
 * @date 2021/5/6 7:04 下午
 */
public class ProxyBeanFactory {

    private final AuthenticateFilterChain filterChain;

    private final Object origin;

    private final AccessControlFailReturn failReturn;

    private final AuthenticationIdentificationAchiever authenticationMetadataAchiever;

    public ProxyBeanFactory(AuthenticateFilterChain filterChain,
                            Object origin,
                            AccessControlFailReturn failReturn,
                            AuthenticationIdentificationAchiever authenticationMetadataAchiever) {
        this.filterChain = filterChain;
        this.origin = origin;
        this.failReturn = failReturn;
        this.authenticationMetadataAchiever = authenticationMetadataAchiever;
    }

    public ProxyBean createProxyBean() {
        ProxyBean.MethodWrapper failReturnMethod = buildFailReturnMethodWrapper(failReturn);
        // 创建Bean容器
        ProxyBean proxyBean = new ProxyBean(origin, failReturnMethod, authenticationMetadataAchiever);
        ProxyBean.MethodWrapper authorizeMethod = buildAuthorizeMethodWrapper(filterChain);
        // 收集所有需要进行权限代理的bean
        List<Method> proxyMethods = collectAclMethod(origin);
        // 先后顺序很重要，因为内部采取Map进行储存，那么如果一个方法被RequireRole和RequireAuthenticate两个注解都标注到，只采取
        // RequireRole的处理方法，因为RequireRole也会进行鉴权
        processRequireAuthenticationAnnotatedMethod(proxyBean, origin, proxyMethods);
        processRequireRolesAnnotatedMethod(proxyBean, origin, proxyMethods);
        return proxyBean;
    }

    private void processRequireAuthenticationAnnotatedMethod(ProxyBean proxyBean, Object origin, List<Method> proxyMethods) {
        boolean globalFlag = hasGlobalAnnotated(origin, RequireAuthentication.class);
        ProxyBean.MethodWrapper authenticateMethod = buildAuthenticateMethodWrapper(filterChain);
        if (globalFlag) {
            for (Method method : proxyMethods) {
                proxyBean.put(method, authenticateMethod);
            }
            return;
        }
        for (Method method : proxyMethods) {
            if (method.isAnnotationPresent(RequireAuthentication.class)) {
                proxyBean.put(method, authenticateMethod);
            }
        }
    }

    private void processRequireRolesAnnotatedMethod(ProxyBean proxyBean, Object origin, List<Method> proxyMethods) {
        boolean globalFlag = hasGlobalAnnotated(origin, RequireRoles.class);
        ProxyBean.MethodWrapper authorizeMethod = buildAuthorizeMethodWrapper(filterChain);
        if (globalFlag) {
            for (Method method : proxyMethods) {
                proxyBean.put(method, authorizeMethod);
            }
            return;
        }
        for (Method method : proxyMethods) {
            if (method.isAnnotationPresent(RequireRoles.class)) {
                proxyBean.put(method, authorizeMethod);
            }
        }
    }


    private boolean hasGlobalAnnotated(Object origin, Class<? extends Annotation> clazz) {
        return origin.getClass().isAnnotationPresent(clazz);
    }


    /**
     * 收集所有需要代理的method
     */
    private List<Method> collectAclMethod(Object origin) {
        Class<?> clazz = origin.getClass();
        if (clazz.isAnnotationPresent(RequireRoles.class) || clazz.isAnnotationPresent(RequireAuthentication.class)) {
            return Arrays.asList(clazz.getDeclaredMethods());
        }
        Method[] methods = clazz.getDeclaredMethods();
        if (ObjectUtils.isEmpty(methods)) {
            return new ArrayList<>();
        }
        List<Method> container = new ArrayList<>();
        for (Method method : methods) {
            if (method.isAnnotationPresent(RequireRoles.class) || clazz.isAnnotationPresent(RequireAuthentication.class)) {
                container.add(method);
            }
        }
        return container;
    }

    private ProxyBean.MethodWrapper buildAuthenticateMethodWrapper(AuthenticateFilterChain filterChain) {
        Class<?> clazz = filterChain.getClass();
        try {
            Method method = clazz.getMethod("authenticate", Identification.class);
            return new ProxyBean.MethodWrapper(method, filterChain);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("No such method, please confirm", e);
        }
    }


    private ProxyBean.MethodWrapper buildAuthorizeMethodWrapper(AuthenticateFilterChain filterChain) {
        Class<?> clazz = filterChain.getClass();
        try {
            Method method = clazz.getMethod("authorized", Identification.class, RoleAccessor.class);
            return new ProxyBean.MethodWrapper(method, filterChain);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("No such method, please confirm", e);
        }
    }

    private ProxyBean.MethodWrapper buildFailReturnMethodWrapper(AccessControlFailReturn failReturn) {
        Class<?> clazz = failReturn.getClass();
        try {
            Method method = clazz.getMethod("failReturn", FilterResult.class);
            return new ProxyBean.MethodWrapper(method, failReturn);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("No such method, please confirm", e);
        }
    }
}
